/*
    TreeViewer - Cross-platform software to draw phylogenetic trees
    Copyright (C) 2023  Giorgio Bianchini, University of Bristol
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

using System;
using TreeViewer;
using VectSharp;
using System.Collections.Generic;
using System.Text;
using VectSharp.SVG;
using System.IO;
using PhyloTree;
using System.Linq;
using System.Runtime.InteropServices;

namespace a10bbbbe1c6344582907311f067a54081
{
    /// <summary>
    /// This module sets up plot action modules to display branch scores. It can be useful e.g. to highlight BLAST scores on the tree.
    /// If the scores are assigned to the tip nodes, you may want to use the _Propagate attribute_ module to propagate the scores up
    /// to the root node.
    /// </summary>
    /// 
    /// <description>
    /// <![CDATA[
    /// ## Further information
    /// 
    /// When this module is activated (either by clicking on the button in the UI, or by enabling in the command line), the user is asked
    /// to select the attribute used to highlight the branches and the range of values. This range is divided into the specified number
    /// of intervals, with every interval being assigned a colour and representing a "layer": each branch is then highlighted with all the
    /// colours corresponding to the intervals assigned to values lower than its score.
    /// 
    /// As a result, branches with low scores will be highlighted only in few colours, corresponding to the left-end side of the selected
    /// gradient, while branches with high scores will be highlighted with many colours spanning the whole gradient.
    /// 
    /// The module also adds a legend to the tree plot; this is generated as an SVG image, attached to the tree, and drawn using the
    /// _Draw image_ module. If you wish to make alterations to this legend image, you can export it from the attachment list. Each
    /// activation of this module will create a new attachment; therefore, if you activate the module multiple times you may want to delete
    /// the legends created by previous instances (in any case, these just take up space and do not interfere with the working of the program).
    /// 
    /// Here is an example of a tree drawn using the style resulting from using this module:
    /// 
    /// <p align="center">
    /// <img height="800" src="">
    /// </p>
    ///
    /// The branches in the bottom part of the image have low scores and are highlighted only using the first few colours of the gradient,
    /// while the branches at the top of the tree have higher scores and are highlighted with multiple concentric colours.
    /// ]]>
    /// </description>
    public static class MyModule
    {
        public const string Name = "Branch score style";
        public const string HelpText = "Sets the plot actions to display node scores using the branch colours.";
        public const string Author = "Giorgio Bianchini";
        public static Version Version = new Version("1.1.3");
        public const ModuleTypes ModuleType = ModuleTypes.Action;

        public const string Id = "10bbbbe1-c634-4582-9073-11f067a54081";

        public static bool IsAvailableInCommandLine { get; } = true;

        public static List<(Avalonia.Input.Key, Avalonia.Input.KeyModifiers)> ShortcutKeys { get; } = new List<(Avalonia.Input.Key, Avalonia.Input.KeyModifiers)>() { (Avalonia.Input.Key.None, Avalonia.Input.KeyModifiers.None) };

        public static bool TriggerInTextBox { get; } = false;

        public static string ButtonText { get; } = "Branch scores";

        public static string GroupName { get; } = "Tree style";

        public static double GroupIndex { get; } = 4;

        public static bool IsLargeButton { get; } = true;

        public static List<(string, Func<double, VectSharp.Page>)> SubItems { get; } = new List<(string, Func<double, VectSharp.Page>)>();

        private static string Icon32Base64 = "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";
        private static string Icon48Base64 = "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";
        private static string Icon64Base64 = "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";

        public static VectSharp.Page GetIcon(double scaling)
        {
            byte[] bytes;

            if (scaling <= 1)
            {
                bytes = Convert.FromBase64String(Icon32Base64);
            }
            else if (scaling <= 1.5)
            {
                bytes = Convert.FromBase64String(Icon48Base64);
            }
            else
            {
                bytes = Convert.FromBase64String(Icon64Base64);
            }

            IntPtr imagePtr = Marshal.AllocHGlobal(bytes.Length);
            Marshal.Copy(bytes, 0, imagePtr, bytes.Length);

            VectSharp.RasterImage icon;

            try
            {
                icon = new VectSharp.MuPDFUtils.RasterImageStream(imagePtr, bytes.Length, MuPDFCore.InputFileTypes.PNG);
            }
            catch (Exception ex)
            {
                throw ex.InnerException;
            }
            finally
            {
                Marshal.FreeHGlobal(imagePtr);
            }

            VectSharp.Page pag = new VectSharp.Page(32, 32);
            pag.Graphics.DrawRasterImage(0, 0, 32, 32, icon);

            return pag;
        }

        private static string GetColourFormatterCode(double min, double max, Gradient gradient)
        {
            StringBuilder codeBuilder = new StringBuilder();
            codeBuilder.AppendLine(@"static Gradient gradient = new Gradient(new List<TreeViewer.GradientStop>()
{");
            int indentationLevel = 1;

            for (int i = 0; i < gradient.GradientStops.Count; i++)
            {
                codeBuilder.Append(' ', indentationLevel * 4);
                codeBuilder.Append("new TreeViewer.GradientStop(");
                codeBuilder.Append(gradient.GradientStops[i].Position.ToString(System.Globalization.CultureInfo.InvariantCulture));
                codeBuilder.Append(", Colour.FromRgba(");
                codeBuilder.Append(gradient.GradientStops[i].Colour.R.ToString(System.Globalization.CultureInfo.InvariantCulture));
                codeBuilder.Append("d, ");
                codeBuilder.Append(gradient.GradientStops[i].Colour.G.ToString(System.Globalization.CultureInfo.InvariantCulture));
                codeBuilder.Append("d, ");
                codeBuilder.Append(gradient.GradientStops[i].Colour.B.ToString(System.Globalization.CultureInfo.InvariantCulture));
                codeBuilder.Append("d, ");
                codeBuilder.Append(gradient.GradientStops[i].Colour.A.ToString(System.Globalization.CultureInfo.InvariantCulture));
                codeBuilder.Append("d))");
                if (i < gradient.GradientStops.Count - 1)
                {
                    codeBuilder.AppendLine(",");
                }
                else
                {
                    codeBuilder.AppendLine();
                }
            }

            codeBuilder.AppendLine("});");

            codeBuilder.Append(@"
public static Colour? Format(object attribute)
{
    if (attribute is double attributeValue && !double.IsNaN(attributeValue))
    {
");
            indentationLevel++;
            codeBuilder.Append(' ', indentationLevel * 4);
            codeBuilder.Append("double position = (attributeValue - ");
            codeBuilder.Append(min.ToString(System.Globalization.CultureInfo.InvariantCulture));
            codeBuilder.Append(") / ");
            codeBuilder.Append((max - min).ToString(System.Globalization.CultureInfo.InvariantCulture));
            codeBuilder.AppendLine(";");
            codeBuilder.Append(' ', indentationLevel * 4);
            codeBuilder.AppendLine("position = Math.Max(Math.Min(position, 1), 0);");
            codeBuilder.Append(' ', indentationLevel * 4);
            codeBuilder.AppendLine("return gradient.GetColour(position);");
            codeBuilder.AppendLine(@"   }
    else
    {
        return null;
    }");
            codeBuilder.Append("}");

            return codeBuilder.ToString();
        }

        public static void PerformAction(int actionIndex, MainWindow window, InstanceStateData stateData)
        {
            if (stateData.Trees != null)
            {
                Dictionary<string, double> attributeMins = new Dictionary<string, double>();
                Dictionary<string, double> attributeMaxs = new Dictionary<string, double>();

                HashSet<string> numberAttributes = new HashSet<string>();

                foreach (TreeNode node in stateData.TransformedTree.GetChildrenRecursiveLazy())
                {
                    foreach (KeyValuePair<string, object> attribute in node.Attributes)
                    {
                        if (attribute.Value is double attributeValue)
                        {
                            numberAttributes.Add(attribute.Key);

                            if (!attributeMins.TryGetValue(attribute.Key, out double currMinValue))
                            {
                                if (!double.IsNaN(attributeValue))
                                {
                                    attributeMins[attribute.Key] = attributeValue;
                                }
                            }
                            else
                            {
                                if (!double.IsNaN(attributeValue))
                                {
                                    attributeMins[attribute.Key] = Math.Min(attributeValue, currMinValue);
                                }
                            }

                            if (!attributeMaxs.TryGetValue(attribute.Key, out double currMaxValue))
                            {
                                if (!double.IsNaN(attributeValue))
                                {
                                    attributeMaxs[attribute.Key] = attributeValue;
                                }
                            }
                            else
                            {
                                if (!double.IsNaN(attributeValue))
                                {
                                    attributeMaxs[attribute.Key] = Math.Max(attributeValue, currMaxValue);
                                }
                            }
                        }
                    }
                }

                List<string> numberAttributesArray = numberAttributes.ToList();

                int preferredAttribute = 0;
                double maxRange = 0;

                for (int i = 0; i < numberAttributesArray.Count; i++)
                {
                    if (numberAttributesArray[i] != "Length" && numberAttributesArray[i] != "Support")
                    {
                        if (attributeMins.TryGetValue(numberAttributesArray[i], out double min) && attributeMaxs.TryGetValue(numberAttributesArray[i], out double max))
                        {
                            if (max - min > maxRange)
                            {
                                maxRange = max - min;
                                preferredAttribute = i;
                            }
                        }
                    }
                }

                if (InstanceStateData.IsUIAvailable)
                {
                    string attributeName;

                    double minValue;
                    double maxValue;
                    int branchCount;

                    ChildWindow parameterWindow = new ChildWindow() { Width = 250, Height = 350, Title = "Branch scores", FontFamily = window.FontFamily, FontSize = 14, Icon = window.Icon, SizeToContent = Avalonia.Controls.SizeToContent.Height, WindowStartupLocation = Avalonia.Controls.WindowStartupLocation.CenterOwner };

                    Avalonia.Controls.Grid headerGrid = new Avalonia.Controls.Grid() { Margin = new Avalonia.Thickness(10) };
                    headerGrid.RowDefinitions.Add(new Avalonia.Controls.RowDefinition(0, Avalonia.Controls.GridUnitType.Auto));
                    headerGrid.RowDefinitions.Add(new Avalonia.Controls.RowDefinition(1, Avalonia.Controls.GridUnitType.Star));
                    headerGrid.ColumnDefinitions.Add(new Avalonia.Controls.ColumnDefinition(0, Avalonia.Controls.GridUnitType.Auto));
                    headerGrid.ColumnDefinitions.Add(new Avalonia.Controls.ColumnDefinition(1, Avalonia.Controls.GridUnitType.Star));
                    parameterWindow.Content = headerGrid;

                    headerGrid.Children.Add(new DPIAwareBox(GetIcon));

                    {
                        Avalonia.Controls.TextBlock blk = new Avalonia.Controls.TextBlock() { FontSize = 16, Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.FromRgb(0, 114, 178)), Text = "Branch scores", Margin = new Avalonia.Thickness(10, 0, 0, 0), VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center };
                        Avalonia.Controls.Grid.SetColumn(blk, 1);
                        headerGrid.Children.Add(blk);
                    }

                    Avalonia.Controls.Grid grd = new Avalonia.Controls.Grid() { Margin = new Avalonia.Thickness(0, 10, 0, 0) };
                    grd.ColumnDefinitions.Add(new Avalonia.Controls.ColumnDefinition(0, Avalonia.Controls.GridUnitType.Auto));
                    grd.ColumnDefinitions.Add(new Avalonia.Controls.ColumnDefinition(1, Avalonia.Controls.GridUnitType.Star));
                    Avalonia.Controls.Grid.SetRow(grd, 1);
                    Avalonia.Controls.Grid.SetColumnSpan(grd, 2);
                    headerGrid.Children.Add(grd);

                    grd.RowDefinitions.Add(new Avalonia.Controls.RowDefinition(1, Avalonia.Controls.GridUnitType.Star));

                    {
                        Avalonia.Controls.TextBlock blk = new Avalonia.Controls.TextBlock() { Text = "Attribute:", VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, Margin = new Avalonia.Thickness(0, 0, 5, 10) };
                        grd.Children.Add(blk);
                    }

                    Avalonia.Controls.ComboBox attributeComboBox = new Avalonia.Controls.ComboBox() { Items = numberAttributesArray, SelectedIndex = preferredAttribute, Margin = new Avalonia.Thickness(0, 0, 0, 10), VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center };
                    Avalonia.Controls.Grid.SetColumn(attributeComboBox, 1);
                    grd.Children.Add(attributeComboBox);

                    grd.RowDefinitions.Add(new Avalonia.Controls.RowDefinition(1, Avalonia.Controls.GridUnitType.Star));

                    {
                        Avalonia.Controls.TextBlock blk = new Avalonia.Controls.TextBlock() { Text = "Minimum:", VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, Margin = new Avalonia.Thickness(0, 0, 5, 10) };
                        Avalonia.Controls.Grid.SetRow(blk, 1);
                        grd.Children.Add(blk);
                    }

                    Avalonia.Controls.NumericUpDown minNud = new Avalonia.Controls.NumericUpDown()
                    {
                        Minimum = double.NegativeInfinity,
                        Maximum = double.PositiveInfinity,
                        Increment = 1,
                        Margin = new Avalonia.Thickness(0, 0, 0, 10),
                        VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
                    };

                    if (attributeMins.TryGetValue(numberAttributesArray[preferredAttribute], out double defaultMinValue))
                    {
                        minNud.Value = defaultMinValue;
                    }

                    Avalonia.Controls.Grid.SetColumn(minNud, 1);
                    Avalonia.Controls.Grid.SetRow(minNud, 1);
                    grd.Children.Add(minNud);

                    grd.RowDefinitions.Add(new Avalonia.Controls.RowDefinition(1, Avalonia.Controls.GridUnitType.Star));

                    {
                        Avalonia.Controls.TextBlock blk = new Avalonia.Controls.TextBlock() { Text = "Maximum:", VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, Margin = new Avalonia.Thickness(0, 0, 5, 10) };
                        Avalonia.Controls.Grid.SetRow(blk, 2);
                        grd.Children.Add(blk);
                    }

                    Avalonia.Controls.NumericUpDown maxNud = new Avalonia.Controls.NumericUpDown()
                    {
                        Minimum = double.NegativeInfinity,
                        Maximum = double.PositiveInfinity,
                        Increment = 1,
                        Margin = new Avalonia.Thickness(0, 0, 0, 10),
                        VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
                    };

                    if (attributeMaxs.TryGetValue(numberAttributesArray[preferredAttribute], out double defaultMaxValue))
                    {
                        maxNud.Value = defaultMaxValue;
                    }

                    Avalonia.Controls.Grid.SetColumn(maxNud, 1);
                    Avalonia.Controls.Grid.SetRow(maxNud, 2);
                    grd.Children.Add(maxNud);

                    grd.RowDefinitions.Add(new Avalonia.Controls.RowDefinition(0, Avalonia.Controls.GridUnitType.Auto));

                    {
                        Avalonia.Controls.TextBlock blk = new Avalonia.Controls.TextBlock() { Text = "For BLAST bit scores, a minimum of 0 and a maximum of 400-600 is recommended.", TextAlignment = Avalonia.Media.TextAlignment.Center, FontStyle = Avalonia.Media.FontStyle.Italic, Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.FromRgb(140, 140, 140)), VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, Margin = new Avalonia.Thickness(0, 0, 5, 10), TextWrapping = Avalonia.Media.TextWrapping.Wrap };
                        Avalonia.Controls.Grid.SetRow(blk, 3);
                        Avalonia.Controls.Grid.SetColumnSpan(blk, 2);
                        grd.Children.Add(blk);
                    }


                    grd.RowDefinitions.Add(new Avalonia.Controls.RowDefinition(1, Avalonia.Controls.GridUnitType.Star));

                    {
                        Avalonia.Controls.TextBlock blk = new Avalonia.Controls.TextBlock() { Text = "Layers:", VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, Margin = new Avalonia.Thickness(0, 0, 5, 10) };
                        Avalonia.Controls.Grid.SetRow(blk, 4);
                        grd.Children.Add(blk);
                    }

                    Avalonia.Controls.NumericUpDown layerNud = new Avalonia.Controls.NumericUpDown()
                    {
                        Minimum = 2,
                        Maximum = double.PositiveInfinity,
                        Increment = 1,
                        FormatString = "0",
                        Margin = new Avalonia.Thickness(0, 0, 0, 10),
                        VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
                        Value = 10
                    };


                    Avalonia.Controls.Grid.SetColumn(layerNud, 1);
                    Avalonia.Controls.Grid.SetRow(layerNud, 4);
                    grd.Children.Add(layerNud);

                    grd.RowDefinitions.Add(new Avalonia.Controls.RowDefinition(0, Avalonia.Controls.GridUnitType.Auto));

                    {
                        Avalonia.Controls.TextBlock blk = new Avalonia.Controls.TextBlock() { Text = "Gradient:", VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, Margin = new Avalonia.Thickness(0, 0, 5, 10) };
                        Avalonia.Controls.Grid.SetRow(blk, 5);
                        grd.Children.Add(blk);
                    }

                    GradientControl gradientControl = new GradientControl() { Gradient = Modules.DefaultGradients["Viridis"], VerticalAlignment = Avalonia.Layout.VerticalAlignment.Stretch, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch };

                    Avalonia.Controls.Button gradientButton = new Avalonia.Controls.Button() { Padding = new Avalonia.Thickness(5), Margin = new Avalonia.Thickness(0, 0, 0, 10), Height = 34, HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Stretch, VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Stretch, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch };
                    gradientButton.Content = gradientControl;

                    Avalonia.Controls.Grid.SetColumn(gradientButton, 1);
                    Avalonia.Controls.Grid.SetRow(gradientButton, 5);
                    grd.Children.Add(gradientButton);

                    gradientButton.Click += async (s, e) =>
                    {
                        EditGradientWindow win = new EditGradientWindow(new Gradient(gradientControl.Gradient.GradientStops), minNud.Value, maxNud.Value);
                        await win.ShowDialog2(parameterWindow);
                        if (win.Result)
                        {
                            gradientControl.Gradient = win.Gradient;
                        }
                    };

                    grd.RowDefinitions.Add(new Avalonia.Controls.RowDefinition(0, Avalonia.Controls.GridUnitType.Auto));

                    Avalonia.Controls.Grid buttonGrid = new Avalonia.Controls.Grid();
                    Avalonia.Controls.Grid.SetRow(buttonGrid, 6);
                    Avalonia.Controls.Grid.SetColumnSpan(buttonGrid, 2);
                    grd.Children.Add(buttonGrid);

                    buttonGrid.ColumnDefinitions.Add(new Avalonia.Controls.ColumnDefinition(1, Avalonia.Controls.GridUnitType.Star));
                    buttonGrid.ColumnDefinitions.Add(new Avalonia.Controls.ColumnDefinition(0, Avalonia.Controls.GridUnitType.Auto));
                    buttonGrid.ColumnDefinitions.Add(new Avalonia.Controls.ColumnDefinition(1, Avalonia.Controls.GridUnitType.Star));
                    buttonGrid.ColumnDefinitions.Add(new Avalonia.Controls.ColumnDefinition(0, Avalonia.Controls.GridUnitType.Auto));
                    buttonGrid.ColumnDefinitions.Add(new Avalonia.Controls.ColumnDefinition(1, Avalonia.Controls.GridUnitType.Star));

                    Avalonia.Controls.Button okButton = new Avalonia.Controls.Button() { Content = "OK", Width = 100, HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center, FontSize = 13 };
                    okButton.Classes.Add("PlainButton");
                    Avalonia.Controls.Grid.SetColumn(okButton, 1);
                    buttonGrid.Children.Add(okButton);

                    Avalonia.Controls.Button cancelButton = new Avalonia.Controls.Button() { Content = "Cancel", Width = 100, HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center, FontSize = 13 };
                    cancelButton.Classes.Add("PlainButton");
                    Avalonia.Controls.Grid.SetColumn(cancelButton, 3);
                    buttonGrid.Children.Add(cancelButton);

                    okButton.Click += (s, e) =>
                    {
                        parameterWindow.Close();

                        attributeName = numberAttributesArray[attributeComboBox.SelectedIndex];
                        minValue = minNud.Value;
                        maxValue = maxNud.Value;
                        branchCount = (int)Math.Round(layerNud.Value);
                        ContinueAction(window, stateData, attributeName, minValue, maxValue, branchCount, gradientControl.Gradient);
                    };

                    cancelButton.Click += (s, e) =>
                    {
                        parameterWindow.Close();
                    };

                    attributeComboBox.SelectionChanged += (s, e) =>
                    {
                        if (attributeMins.TryGetValue(numberAttributesArray[attributeComboBox.SelectedIndex], out double defaultMinValue))
                        {
                            minNud.Value = defaultMinValue;
                        }

                        if (attributeMaxs.TryGetValue(numberAttributesArray[attributeComboBox.SelectedIndex], out double defaultMaxValue))
                        {
                            maxNud.Value = defaultMaxValue;
                        }
                    };

                    parameterWindow.ShowDialog2(window);
                }
                else
                {
                    ConsoleWrapper.WriteLine();
                    ConsoleWrapper.WriteLine("Setting up branch scores");
                    ConsoleWrapper.WriteLine();
                    ConsoleWrapper.WriteLine("Select attribute to use for branch scores. Available attributes are:");
                    ConsoleWrapper.WriteLine();
                    ConsoleWrapper.WriteList(from el in Enumerable.Range(0, numberAttributesArray.Count) select el != preferredAttribute ? new ConsoleTextSpan[] { new ConsoleTextSpan("#" + el.ToString() + " ", 4, ConsoleColor.Cyan), new ConsoleTextSpan(numberAttributesArray[el], 4, ConsoleColor.Blue) } : new ConsoleTextSpan[] { new ConsoleTextSpan("#" + el.ToString() + " ", 4, ConsoleColor.Cyan), new ConsoleTextSpan(numberAttributesArray[el], 4, ConsoleColor.Blue), new ConsoleTextSpan(" (default)", 4, ConsoleColor.Yellow) }, "    ");
                    ConsoleWrapper.WriteLine();

                    string attributeName = "?";
                    bool isValidAttribute = false;

                    while (!isValidAttribute)
                    {
                        ConsoleWrapper.Write(new ConsoleTextSpan[] { new ConsoleTextSpan("Enter attribute "), new ConsoleTextSpan("name", ConsoleColor.Blue), new ConsoleTextSpan(" or "), new ConsoleTextSpan("#number", ConsoleColor.Cyan), new ConsoleTextSpan(" (leave empty for the "), new ConsoleTextSpan("default", ConsoleColor.Yellow), new ConsoleTextSpan("): ") });
                        attributeName = ConsoleWrapper.ReadLine();
                        ConsoleWrapper.WriteLine();

                        if (string.IsNullOrEmpty(attributeName))
                        {
                            attributeName = numberAttributesArray[preferredAttribute];
                            isValidAttribute = true;
                        }
                        else if (attributeName.StartsWith("#"))
                        {
                            if (int.TryParse(attributeName.Substring(1), out int index) && index >= 0 && index < numberAttributesArray.Count)
                            {
                                attributeName = numberAttributesArray[index];
                                isValidAttribute = true;
                            }
                        }
                        else
                        {
                            for (int i = 0; i < numberAttributesArray.Count; i++)
                            {
                                if (numberAttributesArray[i].Equals(attributeName, StringComparison.OrdinalIgnoreCase))
                                {
                                    isValidAttribute = true;
                                    attributeName = numberAttributesArray[i];
                                    break;
                                }
                            }
                        }
                    }

                    ConsoleWrapper.WriteLine(new ConsoleTextSpan[] { new ConsoleTextSpan("Using attribute "), new ConsoleTextSpan(attributeName, ConsoleColor.Blue), new ConsoleTextSpan(".") });
                    ConsoleWrapper.WriteLine();

                    double minValue = 0;
                    double maxValue = 0;

                    if (attributeMins.TryGetValue(numberAttributesArray[preferredAttribute], out double defaultMinValue))
                    {
                        minValue = defaultMinValue;
                    }

                    if (attributeMaxs.TryGetValue(numberAttributesArray[preferredAttribute], out double defaultMaxValue))
                    {
                        maxValue = defaultMaxValue;
                    }

                    ConsoleWrapper.WriteLine(new[] { new ConsoleTextSpan("Default", ConsoleColor.Yellow), new ConsoleTextSpan(" range: "), new ConsoleTextSpan(minValue.ToString(System.Globalization.CultureInfo.InvariantCulture), ConsoleColor.Blue), new ConsoleTextSpan(" - "), new ConsoleTextSpan(maxValue.ToString(System.Globalization.CultureInfo.InvariantCulture), ConsoleColor.Blue) });
                    ConsoleWrapper.WriteLine();

                    bool validMinimum = false;

                    while (!validMinimum)
                    {
                        ConsoleWrapper.Write(new[] { new ConsoleTextSpan("Enter score range "), new ConsoleTextSpan("minimum", ConsoleColor.Blue), new ConsoleTextSpan(" (leave empty for "), new ConsoleTextSpan("default", ConsoleColor.Yellow), new ConsoleTextSpan("): ") });
                        string enteredValue = ConsoleWrapper.ReadLine();

                        if (string.IsNullOrEmpty(enteredValue))
                        {
                            validMinimum = true;
                        }
                        else
                        {
                            if (double.TryParse(enteredValue, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double parsedValue))
                            {
                                validMinimum = true;
                                minValue = parsedValue;
                            }
                        }
                    }

                    bool validMaximum = false;

                    ConsoleWrapper.WriteLine();

                    while (!validMaximum)
                    {
                        ConsoleWrapper.Write(new[] { new ConsoleTextSpan("Enter score range "), new ConsoleTextSpan("maximum", ConsoleColor.Blue), new ConsoleTextSpan(" (leave empty for "), new ConsoleTextSpan("default", ConsoleColor.Yellow), new ConsoleTextSpan("): ") });
                        string enteredValue = ConsoleWrapper.ReadLine();

                        if (string.IsNullOrEmpty(enteredValue))
                        {
                            validMaximum = true;
                        }
                        else
                        {
                            if (double.TryParse(enteredValue, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double parsedValue))
                            {
                                validMaximum = true;
                                maxValue = parsedValue;
                            }
                        }
                    }

                    int layerCount = 10;

                    ConsoleWrapper.WriteLine();
                    ConsoleWrapper.WriteLine(new[] { new ConsoleTextSpan("Score range: "), new ConsoleTextSpan(minValue.ToString(System.Globalization.CultureInfo.InvariantCulture), ConsoleColor.Blue), new ConsoleTextSpan(" - "), new ConsoleTextSpan(maxValue.ToString(System.Globalization.CultureInfo.InvariantCulture), ConsoleColor.Blue) });
                    ConsoleWrapper.WriteLine();
                    ConsoleWrapper.WriteLine(new[] { new ConsoleTextSpan("Default", ConsoleColor.Yellow), new ConsoleTextSpan(" number of layers: "), new ConsoleTextSpan("10", ConsoleColor.Blue) });
                    ConsoleWrapper.WriteLine();

                    bool validLayers = false;

                    while (!validLayers)
                    {
                        ConsoleWrapper.Write(new[] { new ConsoleTextSpan("Enter "), new ConsoleTextSpan("number of layers", ConsoleColor.Blue), new ConsoleTextSpan(" (leave empty for "), new ConsoleTextSpan("default", ConsoleColor.Yellow), new ConsoleTextSpan("): ") });
                        string enteredValue = ConsoleWrapper.ReadLine();

                        if (string.IsNullOrEmpty(enteredValue))
                        {
                            validLayers = true;
                        }
                        else
                        {
                            if (int.TryParse(enteredValue, out int parsedValue))
                            {
                                validLayers = true;
                                layerCount = parsedValue;
                            }
                        }
                    }

                    ConsoleWrapper.WriteLine();
                    ConsoleWrapper.WriteLine(new[] { new ConsoleTextSpan("Using "), new ConsoleTextSpan(layerCount.ToString(), ConsoleColor.Blue), new ConsoleTextSpan(" layers.") });
                    ConsoleWrapper.WriteLine();

                    ConsoleWrapper.WriteLine("Available gradients: ");
                    ConsoleWrapper.WriteLine();
                    ConsoleWrapper.WriteList((from el in Modules.DefaultGradients select el.Key != "Viridis" ? new ConsoleTextSpan[] { new ConsoleTextSpan(el.Key, 4, ConsoleColor.Blue) } : new ConsoleTextSpan[] { new ConsoleTextSpan(el.Key, 4, ConsoleColor.Blue), new ConsoleTextSpan(" (default)", 4, ConsoleColor.Yellow) }).Append(new[] { new ConsoleTextSpan("Custom", ConsoleColor.Blue) }), "    ");

                    Gradient gradient = Modules.DefaultGradients["Viridis"];
                    bool validGradient = false;

                    while (!validGradient)
                    {
                        ConsoleWrapper.WriteLine();
                        ConsoleWrapper.Write(new[] { new ConsoleTextSpan("Enter "), new ConsoleTextSpan("gradient name", ConsoleColor.Blue), new ConsoleTextSpan(" (leave empty for "), new ConsoleTextSpan("default", ConsoleColor.Yellow), new ConsoleTextSpan("): ") });
                        string enteredValue = ConsoleWrapper.ReadLine();

                        if (string.IsNullOrEmpty(enteredValue))
                        {
                            validGradient = true;
                        }
                        else if (!enteredValue.Equals("Custom", StringComparison.OrdinalIgnoreCase))
                        {
                            foreach (KeyValuePair<string, Gradient> kvp in Modules.DefaultGradients)
                            {
                                if (kvp.Key.Equals(enteredValue, StringComparison.OrdinalIgnoreCase))
                                {
                                    validGradient = true;
                                    gradient = kvp.Value;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            ConsoleWrapper.WriteLine();
                            ConsoleWrapper.WriteLine(new[] { new ConsoleTextSpan("Enter gradient stops as "), new ConsoleTextSpan("position colour", ConsoleColor.Blue), new ConsoleTextSpan(".") });
                            ConsoleWrapper.WriteLine(new[] { new ConsoleTextSpan("                  E.g.: "), new ConsoleTextSpan("0.5 #FF0000", ConsoleColor.Blue) });
                            ConsoleWrapper.WriteLine(new[] { new ConsoleTextSpan("Enter "), new ConsoleTextSpan("End", ConsoleColor.Blue), new ConsoleTextSpan(" to finish.") });
                            ConsoleWrapper.WriteLine();

                            bool gradientFinished = false;

                            List<TreeViewer.GradientStop> gradientStops = new List<TreeViewer.GradientStop>();

                            while (!gradientFinished)
                            {
                                string line = ConsoleWrapper.ReadLine();

                                if (line.Equals("End", StringComparison.OrdinalIgnoreCase))
                                {
                                    gradientFinished = true;
                                }
                                else
                                {
                                    bool elementValid = false;

                                    try
                                    {
                                        string positionString = line.Substring(0, line.IndexOf(" "));
                                        string colourString = line.Substring(line.IndexOf(" ") + 1).Trim();

                                        if (double.TryParse(positionString, out double position))
                                        {
                                            Colour? col = Colour.FromCSSString(colourString);

                                            if (col != null)
                                            {
                                                gradientStops.Add(new TreeViewer.GradientStop(position, col.Value));
                                                elementValid = true;
                                            }
                                        }
                                    }
                                    catch { }

                                    if (!elementValid)
                                    {
                                        ConsoleWrapper.WriteLine();
                                        ConsoleWrapper.WriteLine(new ConsoleTextSpan("Invalid gradient stop!", ConsoleColor.Red));
                                        ConsoleWrapper.WriteLine();
                                    }
                                }
                            }

                            if (gradientStops.Count >= 2)
                            {
                                gradient = new Gradient(gradientStops);
                                validGradient = true;
                            }
                        }
                    }

                    ContinueAction(window, stateData, attributeName, minValue, maxValue, layerCount, gradient);
                }
            }
        }

        private static Colour Blend(Colour foreground, Colour background)
        {
            double a = foreground.A + background.A * (1 - foreground.A);

            double r = (foreground.R * foreground.A + background.R * background.A * (1 - foreground.A)) / a;
            double g = (foreground.G * foreground.A + background.G * background.A * (1 - foreground.A)) / a;
            double b = (foreground.B * foreground.A + background.B * background.A * (1 - foreground.A)) / a;

            return Colour.FromRgba(r, g, b, a);
        }

        private static void ContinueAction(MainWindow window, InstanceStateData stateData, string attributeName, double minValue, double maxValue, int branchCount, Gradient fullGradient)
        {
			if (InstanceStateData.IsUIAvailable)
            {
				window.PushUndoFrame(UndoFrameLevel.CoordinatesModule, 0);
			}
			
            double defaultHeight = Math.Min(10000, stateData.TransformedTree.GetLeaves().Count * 14);
            double maxBranchWidth = defaultHeight / 60;
            double minBranchWidth = defaultHeight / 60 * 0.1;

            while (stateData.PlottingModules().Count > 0)
            {
                stateData.RemovePlottingModule(0);
            }

            for (int i = 0; i < branchCount; i++)
            {
                NumberFormatterOptions branchWidth = new NumberFormatterOptions(Modules.DefaultAttributeConvertersToDouble[1]) { AttributeName = "Thickness", AttributeType = "Number", DefaultValue = maxBranchWidth - (maxBranchWidth - minBranchWidth) * i / (branchCount - 1) };

                branchWidth.Parameters = new object[] {
                    Modules.DefaultAttributeConvertersToDouble[1],
                    0.0,
                    double.PositiveInfinity,
                    true
                };

                Colour currCol = fullGradient.GetColour((double)i / (branchCount - 1));

                double posLeft = Math.Max((double)i / branchCount - 0.00001, 0);
                double posRight = Math.Min(posLeft + 0.00001, 1);

                Gradient gradient = new Gradient(new List<TreeViewer.GradientStop>()
                {
                    new TreeViewer.GradientStop(posLeft, currCol.WithAlpha(0)),
                    new TreeViewer.GradientStop(posRight, currCol),
                });

                if (i == 0)
                {
                    gradient = new Gradient(new List<TreeViewer.GradientStop>()
                    {
                        new TreeViewer.GradientStop(0, currCol),
                        new TreeViewer.GradientStop(1, currCol),
                    });
                }

                string colourFormatterSource = GetColourFormatterCode(minValue, maxValue, gradient);
				
				object[] formatterParams = new object[] {
                    colourFormatterSource,
                    minValue,
                    maxValue,
                    gradient,
                    true
                    };

                ColourFormatterOptions branchColour = new ColourFormatterOptions(colourFormatterSource, formatterParams) { AttributeName = attributeName, AttributeType = "Number", DefaultColour = currCol.WithAlpha(0) };

                Action<Dictionary<string, object>> updater = stateData.AddPlottingModule(Modules.GetModule(Modules.PlottingModules, "7c767b07-71be-48b2-8753-b27f3e973570"));
                updater(new Dictionary<string, object>() { { "Root branch", false }, { "Line weight:", branchWidth }, { "Colour:", branchColour } });
            }

            {
                NumberFormatterOptions branchWidth = new NumberFormatterOptions(Modules.DefaultAttributeConvertersToDouble[1]) { AttributeName = "Thickness", AttributeType = "Number", DefaultValue = minBranchWidth * 0.25 };

                branchWidth.Parameters = new object[] {
                    Modules.DefaultAttributeConvertersToDouble[1],
                    0.0,
                    double.PositiveInfinity,
                    true
                    };

                Gradient gradient = Modules.DefaultGradients["TransparentToBlack"];
				
				object[] formatterParams = new object[] {
                    Modules.DefaultAttributeConvertersToColour[0],
                    0,
                    1,
                    gradient,
                    true
                    };

                ColourFormatterOptions branchColour = new ColourFormatterOptions(Modules.DefaultAttributeConvertersToColour[0], formatterParams) { AttributeName = "Color", AttributeType = "String", DefaultColour = Colours.White };


                Action<Dictionary<string, object>> updater = stateData.AddPlottingModule(Modules.GetModule(Modules.PlottingModules, "7c767b07-71be-48b2-8753-b27f3e973570"));
                updater(new Dictionary<string, object>() { { "Root branch", false }, { "Line weight:", branchWidth }, { "Colour:", branchColour } });
            }


            CoordinateModule module = Modules.GetModule(Modules.CoordinateModules, "95b61284-b870-48b9-b51c-3276f7d89df1");

            Action<Dictionary<string, object>> updater2 = stateData.SetCoordinatesModule(module);
            updater2(new Dictionary<string, object>() { { "Width:", defaultHeight }, { "Height:", defaultHeight }, { "Apply", true } });

            {
                Page legend = new Page(100, 10);
                Graphics gpr = legend.Graphics;

                Colour colLeft = Colours.Orange;
                Colour colRight = Colours.Orange;

                for (int i = 0; i < branchCount; i++)
                {
                    Colour currCol = fullGradient.GetColour((double)i / (branchCount - 1));
                    gpr.FillRectangle(100.0 / branchCount * i, 0, 100.0 / branchCount + (i < branchCount - 1 && (fullGradient.GetColour((double)(i + 1) / (branchCount - 1))).A == 1 ? 1 : 0), 10, currCol);

                    if (i == 0)
                    {
                        if (Blend(Blend(currCol, stateData.GraphBackgroundColour), Colours.White).ToLab().L < 0.5)
                        {
                            colLeft = Colours.White;
                        }
                        else
                        {
                            colLeft = Colours.Black;
                        }
                    }

                    if (i == branchCount - 1)
                    {
                        if (Blend(Blend(currCol, stateData.GraphBackgroundColour), Colours.White).ToLab().L < 0.5)
                        {
                            colRight = Colours.White;
                        }
                        else
                        {
                            colRight = Colours.Black;
                        }
                    }
                }

                Font fnt = new Font(FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Helvetica), 7.5);

                gpr.FillText(3, 5, minValue.ToString(System.Globalization.CultureInfo.InvariantCulture), fnt, colLeft, TextBaselines.Middle);
                gpr.FillText(97 - fnt.MeasureText(maxValue.ToString(System.Globalization.CultureInfo.InvariantCulture)).Width, 5, maxValue.ToString(System.Globalization.CultureInfo.InvariantCulture), fnt, colRight, TextBaselines.Middle);

                Stream ms = new MemoryStream();
                legend.SaveAsSVG(ms);
                ms.Seek(0, SeekOrigin.Begin);

                string currName = "ScoreLegend";
                string baseName = "ScoreLegend";
                int currIndex = 0;

                while (stateData.Attachments.ContainsKey(currName))
                {
                    currIndex++;
                    currName = baseName + currIndex.ToString();
                }


                Attachment attachment = new Attachment(currName, true, true, ref ms);
                stateData.Attachments.Add(currName, attachment);

                Dictionary<string, Point> coordinates = module.GetCoordinates(stateData.TransformedTree, stateData.GetCoordinatesModuleParameters());

                Point rootNode = coordinates[stateData.TransformedTree.Id];
                Point ancNode = coordinates[Modules.RootNodeId];

                rootNode = new Point((rootNode.X + ancNode.X) * 0.5, (rootNode.Y + ancNode.Y) * 0.5);

                double minX = double.MaxValue;
                double maxX = double.MinValue;
                double minY = double.MaxValue;
                double maxY = double.MinValue;

                foreach (KeyValuePair<string, Point> kvp in coordinates)
                {
                    minX = Math.Min(kvp.Value.X, minX);
                    maxX = Math.Max(kvp.Value.X, maxX);

                    minY = Math.Min(kvp.Value.Y, minY);
                    maxY = Math.Max(kvp.Value.Y, maxY);
                }

                Font legendFont = new Font(FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.HelveticaBold), maxBranchWidth * 4 / 3);
                double totalWidth = 100 * maxBranchWidth / 10.5 + legendFont.MeasureText(attributeName + "_").Width + 10;

                updater2 = stateData.AddPlottingModule(Modules.GetModule(Modules.PlottingModules, "06888353-e930-4d08-ab24-5727bced8cd6"));
                updater2(new Dictionary<string, object>() { { "Markdown source:", "### **" + attributeName + "** ![](attachment://" + currName + ")" }, { "Font size:", maxBranchWidth }, { "Width:", totalWidth }, { "Position:", new Point(0, maxBranchWidth * 2) } });

            }

            if (InstanceStateData.IsUIAvailable)
            {
                window.BuildAttachmentList();
                window.UpdateCoordinates();
                window.AutoFit();
            }
        }
    }
}